﻿using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Digests;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Crypto.Signers;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.Math.EC;
using Org.BouncyCastle.Security;
using System;

namespace BuddHa.Common.SMUtil.Implementation.V2
{
    internal class SM2SignVerifier
    {
        private readonly IDigest _digest;
        private readonly IDsaEncoding _encoding;

        private ECDomainParameters _ecParams;
        private ECPoint _pubPoint;
        private ECKeyParameters _ecKey;
        private byte[] _z;
        public SM2SignVerifier() : this(StandardDsaEncoding.Instance, new SM3Digest())
        {

        }

        public SM2SignVerifier(IDsaEncoding encoding, IDigest digest)
        {
            _encoding = encoding;
            _digest = digest;
        }

        public virtual void Init(ICipherParameters parameters, byte[] userId)
        {
            _ecKey = (ECKeyParameters)parameters;
            _ecParams = _ecKey.Parameters;
            _pubPoint = ((ECPublicKeyParameters)_ecKey).Q;
            if (userId.Length >= 8192)
                throw new ArgumentException("SM2 UserID必须小于2^16 bits long");
            _z = GetZ(userId);
        }


        public virtual bool VerifySignature(byte[] data, BigInteger rBigInteger, BigInteger sBigInteger)
        {
            try
            {
                _digest.Reset();
                _digest.BlockUpdate(_z, 0, _z.Length);
                _digest.BlockUpdate(data, 0, data.Length);
                return VerifySignature(rBigInteger, sBigInteger);
            }
            catch (Exception ex)
            {
            }
            finally
            {

            }
            return false;
        }

        private BigInteger CalculateE(BigInteger n, byte[] message)
        {
            // TODO Should hashes larger than the order be truncated as with ECDSA?
            return new BigInteger(1, message);
        }

        private bool VerifySignature(BigInteger r, BigInteger s)
        {

            BigInteger n = _ecParams.N;

            // 5.3.1 Draft RFC:  SM2 Public Key Algorithms
            // B1
            if (r.CompareTo(BigInteger.One) < 0 || r.CompareTo(n) >= 0)
                return false;

            // B2
            if (s.CompareTo(BigInteger.One) < 0 || s.CompareTo(n) >= 0)
                return false;

            // B3
            byte[] eHash = DigestUtilities.DoFinal(_digest);

            // B4
            BigInteger e = CalculateE(n, eHash);

            // B5
            BigInteger t = r.Add(s).Mod(n);
            if (t.SignValue == 0)
                return false;

            // B6
            //ECPoint q = ((ECPublicKeyParameters)_ecKey).Q;
            ECPoint x1y1 = ECAlgorithms.SumOfTwoMultiplies(_ecParams.G, s, _pubPoint, t).Normalize();
            if (x1y1.IsInfinity)
                return false;

            // B7
            return r.Equals(e.Add(x1y1.AffineXCoord.ToBigInteger()).Mod(n));
        }

        private byte[] GetZ(byte[] userID)
        {
            AddUserID(_digest, userID);

            AddFieldElement(_digest, _ecParams.Curve.A);
            AddFieldElement(_digest, _ecParams.Curve.B);
            AddFieldElement(_digest, _ecParams.G.AffineXCoord);
            AddFieldElement(_digest, _ecParams.G.AffineYCoord);
            AddFieldElement(_digest, _pubPoint.AffineXCoord);
            AddFieldElement(_digest, _pubPoint.AffineYCoord);

            return DigestUtilities.DoFinal(_digest);
        }

        private void AddUserID(IDigest digest, byte[] userID)
        {
            int len = userID.Length * 8;
            digest.Update((byte)(len >> 8));
            digest.Update((byte)len);
            digest.BlockUpdate(userID, 0, userID.Length);
        }

        private void AddFieldElement(IDigest digest, ECFieldElement v)
        {
            byte[] p = v.GetEncoded();
            digest.BlockUpdate(p, 0, p.Length);
        }

    }
}
